using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;

namespace KaroGame.FrontEnd
{
    public class KaroCone : Microsoft.Xna.Framework.DrawableGameComponent
    {
        #region Properties
        private readonly float CONE_SPEED = 0.01f;
        private ConeType coneType;
        private Model coneModel;
        private int tileId;
        private static readonly Matrix OFFSET_TO_TILE_UNFLIPPED = Matrix.CreateTranslation( new Vector3( 0, 0, 0.65f ) );
        private static readonly Matrix ROTATION_CONE_UNFLIPPED = Matrix.CreateRotationX( MathHelper.ToRadians( 180 ) );
        private bool isFlipped;
        private Matrix translationToPosition;
        private Vector3 defaultDiffuseColor;
        private KaroBoundingBox boundingBox;

        private Matrix tilePosition;
        private float flipDegreesX;
        private float flipDegreesZ;
        private float nrOfStepsRequired;
        private int currentStep;
        private bool animatingMovement;
        private bool animatingFlip;

        private bool activated;
        private bool selected;

        Random r = new Random();
        private int currentDanceStep;
        private int danceLoopLength;

        public ConeType ConeType
        {
            get { return coneType; }
        }

        public bool Selected
        {
            get { return selected; }
            set { selected = value; }
        }

        public int TileId
        {
            get { return tileId; }
            set { tileId = value; }
        } 
        #endregion

        #region Constructors
        public KaroCone(Game game, Model coneModel, ConeType coneType, int tileId)
            : base(game)
        {
            this.coneModel = coneModel;
            this.coneType = coneType;
            this.tileId = tileId;
            this.boundingBox = new KaroBoundingBox((BoundingBox)coneModel.Tag, ((KaroGame)Game).BasicEffect, ((KaroGame)Game).GraphicsDevice);
            this.defaultDiffuseColor = ((BasicEffect)coneModel.Meshes[0].Effects[0]).DiffuseColor;

            isFlipped = false;
            animatingMovement = false;
            animatingFlip = false;
            flipDegreesX = 0;
            flipDegreesZ = 0;
            currentStep = 0;
            nrOfStepsRequired = -1;

            danceLoopLength = 60 + 2 * r.Next( 20 );    // Even number between 60 & 100
            currentDanceStep = danceLoopLength - 1;     // This way the 1st dance will initiate random flipDegreesZ
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            Move(tileId);

            base.Initialize();
        }
        #endregion

        #region Public Methods
        public void Move(int newTileId)
        {
            this.tileId = newTileId;

            translationToPosition = ((ITileProvider)Game.Services.GetService(typeof(ITileProvider))).GetById(tileId).TranslationToPosition;
        }

        public void MoveAnimated( int newTileId, bool shouldFlip )
        {
            animatingMovement = true;
            animatingFlip = shouldFlip;

            ( (KaroGame)Game ).IsMovingConeAnimating = true;

            this.tileId = newTileId;

            tilePosition = ( (ITileProvider)Game.Services.GetService( typeof( ITileProvider ) ) ).GetById( tileId ).TranslationToPosition;

            nrOfStepsRequired = (int)( Math.Max(
                Math.Abs( translationToPosition.M41 - tilePosition.M41 ),
                Math.Abs( translationToPosition.M42 - tilePosition.M42 ) ) / CONE_SPEED );

            if ( shouldFlip )
            {
                bool movesPositiveX = translationToPosition.M41 < tilePosition.M41;
                bool movesNegativeX = translationToPosition.M41 > tilePosition.M41;
                bool movesPositiveY = translationToPosition.M42 < tilePosition.M42;
                bool movesNegativeY = translationToPosition.M42 > tilePosition.M42;

                // Determine which direction it will flip
                if ( movesPositiveX )
                {
                    if ( movesPositiveY )
                        flipDegreesZ = 225;
                    else if ( movesNegativeY )
                        flipDegreesZ = 315;
                    else
                        flipDegreesZ = 270;
                }
                else if ( movesNegativeX )
                {
                    if ( movesPositiveY )
                        flipDegreesZ = 135;
                    else if ( movesNegativeY )
                        flipDegreesZ = 45;
                    else
                        flipDegreesZ = 90;
                }
                else
                {
                    if ( movesPositiveY )
                        flipDegreesZ = 180;
                    else
                        flipDegreesZ = 0;
                }
            }
        }

        public float? Intersects(Ray mouseRay)
        {
            Matrix transformation = GetTransformation();

            return boundingBox.Intersects(mouseRay, transformation);
        }

        public void Dance()
        {
            // Jump up
            if ( currentDanceStep < danceLoopLength / 2 )
            {
                translationToPosition *= Matrix.CreateTranslation(0, 0, CONE_SPEED);
                flipDegreesX++;
            }
            else
            {
                // Reached bottom after jump, set new random dance values
                if ( currentDanceStep == danceLoopLength - 1 )
                {
                    currentDanceStep = 0;
                    danceLoopLength = 60 + 2 * r.Next( 20 );  // Even number between 60 & 100
                    flipDegreesZ += r.Next( -180, 180 );
                }
                    // Fall down
                else
                {
                    translationToPosition *= Matrix.CreateTranslation( 0, 0, -CONE_SPEED );
                    flipDegreesX--;
                }
            }
            currentDanceStep++;
        }

        #endregion

        #region Private Methods
        private Matrix GetTransformation()
        {
            return
                Matrix.CreateRotationX(MathHelper.ToRadians(flipDegreesX)) *// Rotate model to get flipped
                Matrix.CreateRotationZ(MathHelper.ToRadians(flipDegreesZ)) *// Rotate model to flip in right direction
                ROTATION_CONE_UNFLIPPED *                                       // Rotate model to get in unflipped (default) position 
                translationToPosition *                                         // Translate model to a tile location
                OFFSET_TO_TILE_UNFLIPPED *                                      // Translate model to get in default (at tile) position
                ((KaroGame)Game).GetWorldMatrix;
        }
        #endregion

        #region UpdateDraw Cycle
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if ( animatingMovement )
            {
                bool isXpositioned = ( translationToPosition.M41 == tilePosition.M41 );
                bool isYpositioned = ( translationToPosition.M42 == tilePosition.M42 );

                bool movesPositiveX = translationToPosition.M41 < tilePosition.M41;
                bool movesPositiveY = translationToPosition.M42 < tilePosition.M42;

                // If destination is not reached yet
                if ( isXpositioned && isYpositioned )
                {
                    ( (KaroGame)Game ).IsMovingConeAnimating = false;
                    animatingMovement = false;

                    if ( animatingFlip )
                    {
                        isFlipped = !isFlipped;
                        animatingFlip = false;
                        flipDegreesZ = 0;

                        // Correction Z direction after flip
                        translationToPosition.M43 = (float)Math.Round( translationToPosition.M43 * 10 ) / 10;
                    }

                    currentStep = 0;
                }

                // For X,Y & Z, update 'conePosition' to 'matchingTilePosition'
                // If deltaCoordinate is smaller than the CONE_SPEED, just copy the value (to prevent jitter)
                if ( Math.Abs( tilePosition.M41 - translationToPosition.M41 ) < CONE_SPEED )
                    translationToPosition.M41 = tilePosition.M41;
                else if ( movesPositiveX )
                    translationToPosition *= Matrix.CreateTranslation( CONE_SPEED, 0, 0 );
                else
                    translationToPosition *= Matrix.CreateTranslation( -CONE_SPEED, 0, 0 );

                if ( Math.Abs( tilePosition.M42 - translationToPosition.M42 ) < CONE_SPEED )
                    translationToPosition.M42 = tilePosition.M42;
                else if ( movesPositiveY )
                    translationToPosition *= Matrix.CreateTranslation( 0, CONE_SPEED, 0 );
                else
                    translationToPosition *= Matrix.CreateTranslation( 0, -CONE_SPEED, 0 );

                if ( animatingFlip )
                {
                    flipDegreesX += 180 / nrOfStepsRequired;

                    if ( currentStep < ( nrOfStepsRequired / 2 ) )
                    {
                        translationToPosition *= Matrix.CreateTranslation( 0, 0, CONE_SPEED / 2 );
                    }
                    else
                    {
                        translationToPosition *= Matrix.CreateTranslation( 0, 0, -CONE_SPEED / 2 );
                    }

                    // When flipping, decrease height of cone 2nd part of the flip (for smooth look)
                    if ( !isFlipped && currentStep > ( nrOfStepsRequired / 2 ) )
                    {
                        translationToPosition *= Matrix.CreateTranslation( 0, 0, -CONE_SPEED / 2 );
                    }
                    // When flipping, increase height of cone 1st part of the flip (for smooth look)
                    else if ( isFlipped && currentStep < ( nrOfStepsRequired / 2 ) )
                    {
                        translationToPosition *= Matrix.CreateTranslation( 0, 0, CONE_SPEED / 2 );
                    }

                    if ( isFlipped )
                    {
                        // When going to flip from upside down to normal
                        if ( flipDegreesX > 360 )
                            flipDegreesX = 0;
                    }
                    else
                    {
                        // When going to flip to upside down
                        if ( flipDegreesX > 180 )
                            flipDegreesX = 180;
                    }

                    currentStep++;
                }
            }

            if ( activated )
            {
                DebugController.AddDebugGameInfo("On Tile: " + tileId);
                DebugController.AddDebugGameInfo("Is Flipped: " + isFlipped);
                DebugController.AddDebugGameInfo( "Degrees FlippedX: " + flipDegreesX % 360 );
                DebugController.AddDebugGameInfo( "Degrees FlippedZ: " + flipDegreesZ % 360 );
                DebugController.AddDebugGameInfo( "TilePosition: " + new Vector3( tilePosition.M41, tilePosition.M42, tilePosition.M43 ).ToString() );
                DebugController.AddDebugGameInfo( "TranslationToPosition: " + new Vector3( translationToPosition.M41, translationToPosition.M42, translationToPosition.M43 ).ToString() );
                DebugController.AddDebugAnimationOrErrorInfo( "CurrentDanceStep: " + currentDanceStep );
            }

            base.Update(gameTime);
        }
        
        /// <summary>
        /// Allows the game component to draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {
            foreach (ModelMesh coneMesh in coneModel.Meshes)
            {
                foreach (BasicEffect coneEffect in coneMesh.Effects)
                {
                    coneEffect.World = GetTransformation();
                    coneEffect.View = ((KaroGame)Game).BasicEffect.View;
                    coneEffect.Projection = ((KaroGame)Game).BasicEffect.Projection;

                    Vector3 lightColor = Color.TransparentWhite.ToVector3();
                    if (activated)
                    {
                        switch (coneType)
                        {
                            case ConeType.White:
                                lightColor = new Vector3(0.75f, 0.75f, 0.75f);
                                break;
                            case ConeType.Red:
                                lightColor = new Vector3(0.5f, 0.5f, 1.5f);
                                break;
                        }
                    }

                    if (selected)
                    {
                        coneEffect.DiffuseColor = Color.Yellow.ToVector3();
                    }

                    else
                    {
                        coneEffect.DiffuseColor = defaultDiffuseColor;
                    }

                    coneEffect.LightingEnabled = true;
                    coneEffect.DirectionalLight0.Enabled = true;
                    coneEffect.DirectionalLight0.DiffuseColor = lightColor;
                    coneEffect.DirectionalLight0.SpecularColor = Color.White.ToVector3();
                    coneEffect.DirectionalLight0.Direction = new Vector3(1, 0, -1);
                    coneEffect.DirectionalLight1.Enabled = true;
                    coneEffect.DirectionalLight1.DiffuseColor = lightColor;
                    coneEffect.DirectionalLight1.SpecularColor = Color.White.ToVector3();
                    coneEffect.DirectionalLight1.Direction = new Vector3(-1, 0, 0);
                }

                coneMesh.Draw();
            }

            // draw cone bounding box if asked so
            if (DebugController.IsDebugMode)
            {
                Matrix transformation = GetTransformation();
                boundingBox.Draw(transformation);
            }

            base.Draw(gameTime);
        }

        public void Activate()
        {
            activated = true;
            boundingBox.Activate();
        }

        public void Deactivate()
        {
            activated = false;
            boundingBox.Deactivate();
        }

        public void Select()
        {
            selected = true;
        }

        public void Deselect()
        {
            selected = false;
        }
        #endregion
    }
}